package NodePath;

use strict;
use warnings;

use File::Spec;
use File::Spec::Unix;

use Constants qw( :dir :file :suffix :toc :vinfo :source );
use TOCInfo;
use AddonPath;
use AddonInfo;

BEGIN {
    eval { require Text::Tabs; } and eval { import Text::Tabs(); };
}
my $has_tabs = ( defined &Text::Tabs::expand );

BEGIN {
    eval { require YAML; } and eval { import YAML(); };
}
my $has_yaml = ( defined &YAML::Load );

BEGIN {
    use Exporter ();
    our ( $VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS );

    $VERSION     = sprintf( "%d", q$Revision$ =~ m{\d+} );
    @ISA         = qw(Exporter);
    @EXPORT      = qw();
    %EXPORT_TAGS = ();
    @EXPORT_OK   = qw();
} ## end BEGIN

# Fields
use constant field_addon_path => 0;
use constant field_rel_dir    => 1;
use constant field_name       => 2;
use constant field_ls_key     => 3;
use constant field_ls_dirs    => 4;
use constant field_ls_files   => 5;

sub new($$$ );
sub get_subnode_path($$ );
sub get_addon_path($ )  { shift->[ &field_addon_path ]; }
sub get_rel_dir($ )     { shift->[ &field_rel_dir ]; }
sub get_name($ )        { shift->[ &field_name ]; }
sub p_get_ls_key($ )    { $_[ 0 ]->[ &field_ls_key ]; }
sub p_set_ls_key($$ )   { $_[ 0 ]->[ &field_ls_key ] = $_[ 1 ]; $_[ 0 ]; }
sub p_get_ls_dirs($ )   { $_[ 0 ]->[ &field_ls_dirs ]; }
sub p_set_ls_dirs($$ )  { $_[ 0 ]->[ &field_ls_dirs ] = $_[ 1 ]; $_[ 0 ]; }
sub p_get_ls_files($ )  { $_[ 0 ]->[ &field_ls_files ]; }
sub p_set_ls_files($$ ) { $_[ 0 ]->[ &field_ls_files ] = $_[ 1 ]; $_[ 0 ]; }
sub ls($ );
sub is_file($ );
sub is_in_svn($ );
sub get_version_info($ );
sub get_revision($ );
sub get_source_url($ );
sub p_get_yaml($ );
sub get_externals($ );
sub get_move_folders($ );
sub get_ignored($ );
sub force_externals_embedded($ );
sub get_key($ );
sub get_contents($ );
sub replace_contents($$ );
sub copy_to($$ );
sub diff_from($$;$ );
sub get_virtual_path($ );

### Handle some moving pains for some externals
our %canonical_url_map = (
    "http://svn.wowace.com/wowace/branches/LibFuBarPlugin-3.0/FrameworkIndependent" => "svn://svn.wowace.com/wow/libfubarplugin-3-0/mainline/trunk",

    "http://svn.wowace.com/wowace/trunk/LibQuickHealth-1.0" => "svn://svn.wowace.com/wow/libquickhealth-1-0/mainline/trunk",

    "http://svn.wowace.com/wowace/tags/LibStub/" => "svn://svn.wowace.com/wow/libstub/mainline/tags/",

    "http://svn.wowace.com/wowace/trunk/LibStub" => "svn://svn.wowace.com/wow/libstub/mainline/trunk",

    "http://svn.wowace.com/wowace/trunk/Ace2/" => "svn://svn.wowace.com/wow/ace2/mainline/trunk/",

    "http://svn.wowace.com/wowace/trunk/Ace3/" => "svn://svn.wowace.com/wow/ace3/mainline/trunk/",

    "http://svn.wowace.com/wowace/trunk/Babble-2.2/" => "svn://svn.wowace.com/wow/babble-2-2/mainline/trunk/",

    "http://svn.wowace.com/wowace/trunk/CandyBar/" => "svn://svn.wowace.com/wow/candybar/mainline/trunk/",

    "http://svn.wowace.com/wowace/trunk/FuBarPlugin-2.0" => "svn://svn.wowace.com/wow/fubarplugin-2-0/mainline/trunk",

    "http://svn.wowace.com/wowace/trunk/LibAbacus-3.0" => "svn://svn.wowace.com/wow/libabacus-3-0/mainline/trunk",

    "http://svn.wowace.com/wowace/trunk/LibBabble-Faction-3.0" => "svn://svn.wowace.com/wow/libbabble-faction-3-0/mainline/trunk",

    "http://svn.wowace.com/wowace/trunk/LibBabble-Zone-3.0" => "svn://svn.wowace.com/wow/libbabble-zone-3-0/mainline/trunk",

    "http://svn.wowace.com/wowace/trunk/LibRock-1.0" => "svn://svn.wowace.com/wow/librock-1-0/mainline/trunk",

    "http://svn.wowace.com/wowace/trunk/LibSharedMedia-3.0/" => "svn://svn.wowace.com/wow/libsharedmedia-3-0/mainline/trunk/",

    "http://svn.wowace.com/wowace/trunk/PaintChipsLib/" => "svn://svn.wowace.com/wow/paintchipslib/mainline/trunk/",

    "http://svn.wowace.com/wowace/trunk/TabletLib/" => "svn://svn.wowace.com/wow/tabletlib/mainline/trunk/",

    "svn://svn.wowace.com/wow/abacus-lib/" => "svn://svn.wowace.com/wow/abacuslib/",

    "svn://svn.wowace.com/wow/compost-lib/" => "svn://svn.wowace.com/wow/compostlib/",

    "svn://svn.wowace.com/wow/crayon-lib/" => "svn://svn.wowace.com/wow/crayonlib/",

    "svn://svn.wowace.com/wow/fu-bar-plugin-2-0/" => "svn://svn.wowace.com/wow/fubarplugin-2-0/",

    "svn://svn.wowace.com/wow/gratuity-lib/" => "svn://svn.wowace.com/wow/gratuitylib/",

    "svn://svn.wowace.com/wow/item-bonus-lib/" => "svn://svn.wowace.com/wow/itembonuslib/",

    "svn://svn.wowace.com/wow/lib-abacus-3-0/" => "svn://svn.wowace.com/wow/libabacus-3-0/",

    "svn://svn.wowace.com/wow/lib-babble-class-3-0/" => "svn://svn.wowace.com/wow/libbabble-class-3-0/",

    "svn://svn.wowace.com/wow/lib-babble-creature-type-3-0/" => "svn://svn.wowace.com/wow/libbabble-creaturetype-3-0/",

    "svn://svn.wowace.com/wow/lib-babble-inventory-3-0/" => "svn://svn.wowace.com/wow/libbabble-inventory-3-0/",

    "svn://svn.wowace.com/wow/lib-babble-spell-3-0/" => "svn://svn.wowace.com/wow/libbabble-spell-3-0/",

    "svn://svn.wowace.com/wow/lib-babble-zone-3-0/" => "svn://svn.wowace.com/wow/libbabble-zone-3-0/",

    "svn://svn.wowace.com/wow/lib-bars-1-0/" => "svn://svn.wowace.com/wow/libbars-1-0/",

    "svn://svn.wowace.com/wow/lib-crayon-3-0/" => "svn://svn.wowace.com/wow/libcrayon-3-0/",

    "svn://svn.wowace.com/wow/lib-friends-1-0/" => "svn://svn.wowace.com/wow/libfriends-1-0/",

    "svn://svn.wowace.com/wow/lib-fu-bar-plugin-3-0/" => "svn://svn.wowace.com/wow/libfubarplugin-3-0/",

    "svn://svn.wowace.com/wow/lib-gratuity-3-0/" => "svn://svn.wowace.com/wow/libgratuity-3-0/",

    "svn://svn.wowace.com/wow/lib-guild-1-0/" => "svn://svn.wowace.com/wow/libguild-1-0/",

    "svn://svn.wowace.com/wow/lib-jostle-3-0/" => "svn://svn.wowace.com/wow/libjostle-3-0/",

    "svn://svn.wowace.com/wow/lib-periodic-table-3-1/" => "svn://svn.wowace.com/wow/libperiodictable-3-1/",

    "svn://svn.wowace.com/wow/lib-quixote-2-0/" => "svn://svn.wowace.com/wow/libquixote-2-0/",

    "svn://svn.wowace.com/wow/lib-rock-1-0/" => "svn://svn.wowace.com/wow/librock-1-0/",

    "svn://svn.wowace.com/wow/lib-rock-config-1-0/" => "svn://svn.wowace.com/wow/librockconfig-1-0/",

    "svn://svn.wowace.com/wow/lib-rock-db-1-0/" => "svn://svn.wowace.com/wow/librockdb-1-0/",

    "svn://svn.wowace.com/wow/lib-rock-event-1-0/" => "svn://svn.wowace.com/wow/librockevent-1-0/",

    "svn://svn.wowace.com/wow/lib-rock-locale-1-0/" => "svn://svn.wowace.com/wow/librocklocale-1-0/",

    "svn://svn.wowace.com/wow/lib-rock-timer-1-0/" => "svn://svn.wowace.com/wow/librocktimer-1-0/",

    "svn://svn.wowace.com/wow/lib-shared-media-3-0/" => "svn://svn.wowace.com/wow/libsharedmedia-3-0/",

    "svn://svn.wowace.com/wow/lib-simple-frame-1-0/" => "svn://svn.wowace.com/wow/libsimpleframe-1-0/",

    "svn://svn.wowace.com/wow/lib-sink-2-0/" => "svn://svn.wowace.com/wow/libsink-2-0/",

    "svn://svn.wowace.com/wow/lib-tourist-3-0/" => "svn://svn.wowace.com/wow/libtourist-3-0/",

    "svn://svn.wowace.com/wow/roster-lib/" => "svn://svn.wowace.com/wow/rosterlib/",

    "svn://svn.wowace.com/wow/special-events-embed/" => "svn://svn.wowace.com/wow/specialeventsembed/",

    "svn://svn.wowace.com/wow/stat-frame-lib/" => "svn://svn.wowace.com/wow/statframelib/",

    "svn://svn.wowace.com/wow/stat-logic-lib/" => "svn://svn.wowace.com/wow/statlogiclib/",

    "svn://svn.wowace.com/wow/tip-hooker-lib/" => "svn://svn.wowace.com/wow/tiphookerlib/",

    "svn://svn.wowace.com/wow/wowace-dewdrop-lib/" => "svn://svn.wowace.com/wow/dewdroplib/",

    "svn://svn.wowace.com/wow/wowace-tablet-lib/" => "svn://svn.wowace.com/wow/tabletlib/",

);

our %canonical_url = ();

sub fix_external($ ) {
    my $url = shift;
    unless ( exists $canonical_url{ $url } ) {
        foreach my $prefix ( keys %canonical_url_map ) {
            if ( substr( $url, 0, length( $prefix ) ) eq $prefix ) {
                my $res = $canonical_url_map{ $prefix } . substr( $url, length( $prefix ) );
                $canonical_url{ $url } = $res;
                return $res;
            }
        } ## end foreach my $prefix ( keys %canonical_url_map)
        $canonical_url{ $url } = $url;
        return $url;
    } ## end unless ( exists $canonical_url...)
    return $canonical_url{ $url };
} ## end sub fix_external($ )

sub new($$$ ) {
    my ( $this, $path, $rel ) = @_;
    my $class = ref( $this ) || $this;
    $rel = File::Spec->canonpath( $rel );
    my $name = ( File::Spec->splitpath( $rel ) )[ 2 ];
    $name = $path->get_base_name if $name eq "";
    my $self = [ $path, $rel, $name ];
    return bless $self, $class;
} ## end sub new($$$ )

sub get_subnode_path($$ ) {
    my ( $self, $rel ) = @_;
    my $base = $self->get_rel_dir;
    $rel = ( $base eq "" ? $rel : File::Spec->catfile( $base, $rel ) );
    return $self->get_addon_path->get_node_path( $rel );
} ## end sub get_subnode_path($$ )

sub get_toc($;$ ) {
    my $self    = shift;
    my $tocname = shift;
    $tocname = $self->get_name . &SUFFIX_TOC unless defined $tocname;
    my $tocnode = $self->get_subnode_path( $tocname );
    return TOCInfo->load_from_path( $tocnode );
} ## end sub get_toc($;$ )

sub ls($ ) {
    my $self = shift;
    my $key  = $self->get_key;
    if ( !defined $self->p_get_ls_key
        || $self->p_get_ls_key ne $key )
    {
        $self->p_set_ls_key( $key );
        @{ $self }[ &field_ls_dirs, &field_ls_files ] = $self->get_addon_path->p_node_ls( $self->get_rel_dir );
    } ## end if ( !defined $self->p_get_ls_key...)
    return @{ $self }[ &field_ls_dirs, &field_ls_files ];
} ## end sub ls($ )

sub is_file($ ) {
    my $self = shift;
    return $self->get_addon_path->p_node_is_file( $self->get_rel_dir );
}

sub is_in_svn($ ) {
    my $self = shift;
    my $base = $self->get_rel_dir;
    my $rel  = File::Spec->catfile( &DIR_SVN, &FILE_SVN_ENTRIES );
    $rel = ( $base eq "" ? $rel : File::Spec->catfile( $base, $rel ) );
    return $self->get_addon_path->p_node_is_file( $rel );
} ## end sub is_in_svn($ )

sub get_version_info($ ) {
    my $self = shift;
    return $self->get_addon_path->p_node_version_info( $self->get_rel_dir );
}

sub get_revision($ ) {
    my $self = shift;

    if ( $self->get_addon_path->is_stage_dir ) {
        my ( $entry_name, $rev, $minor_rev, $extension ) = split_entry( $self->get_addon_path->get_entry );
        if ( defined $rev ) {
            return $rev, $rev if wantarray;
            return $rev;
        }
    } ## end if ( $self->get_addon_path...)

    my $version_info = $self->get_version_info;
    if ( $version_info ) {
        my $rev      = $version_info->{ &VINFO_DIR_REVISION };
        my $last_rev = $version_info->{ &VINFO_FILE_REVISION };
        $rev      = $last_rev unless defined $rev;
        $last_rev = $rev      unless defined $last_rev;
        if ( defined $rev ) {
            return $rev, $last_rev if wantarray;
            return $rev;
        }
    } ## end if ( $version_info )

    my $toc = $self->get_toc;
    if ( $toc ) {
        my $rev = $toc->get_tag( &TOC_ADDONPKG_REVISION );
        if ( defined $rev ) {
            return $rev, $rev if wantarray;
            return $rev;
        }
    } ## end if ( $toc )

    my ( $dirs, $files ) = $self->ls;
    my $best_revision = 0;
    foreach my $file ( keys %$files ) {
        if (   $file =~ m{^Changelog-.*-r(\d+)\.xml$}i
            || $file =~ m{^Changelog-.*-r(\d+)\.txt$}i )
        {
            $best_revision = $1 if $1 > $best_revision;
        }
    } ## end foreach my $file ( keys %$files)
    undef $best_revision unless $best_revision;
    return $best_revision, $best_revision if wantarray;
    return $best_revision;
} ## end sub get_revision($ )

sub get_source_url($ ) {
    my $self = shift;

    my $version_info = $self->get_version_info;
    if ( $version_info ) {
        my $url = $version_info->{ &VINFO_URL };
        return $url if $url;
    }

    my $toc = $self->get_toc;
    if ( $toc ) {
        my $url = $toc->get_tag( &TOC_ADDONPKG_URL );
        return $url if $url;
    }

    if ( $self->get_addon_path->is_stage_dir ) {
        my $try_file = $self->get_addon_path->get_real_path;
        if ( -f $try_file ) {
            my $addon_info = AddonInfo->lookup( $self->get_addon_path->get_base_name );
            if ( $addon_info ) {
                my $source_type = $addon_info->get_source_type;
                if (   $source_type eq &SOURCE_SVN
                    || $source_type eq &SOURCE_GIT
                    || $source_type eq &SOURCE_HG )
                {
                    my $url = $addon_info->get_url( $source_type );
                    if ( $url ) {
                        my $uri    = URI->new( $url );
                        my @pieces = $uri->path_segments;
                        my @rel    = File::Spec->splitdir( $self->get_rel_dir );
                        shift @rel;
                        $uri->path_segments( @pieces, @rel );
                        return $uri->canonical->as_string;
                    } ## end if ( $url )
                } ## end if ( $source_type eq &SOURCE_SVN...)
            } ## end if ( $addon_info )
        } ## end if ( -f $try_file )
    } ## end if ( $self->get_addon_path...)
    return undef;
} ## end sub get_source_url($ )

my %yaml_record;

sub p_get_yaml($ ) {
    my $self = shift;
    my $key  = $self->get_key;
    return undef unless defined $key;
    unless ( exists $yaml_record{ $key } ) {
        $yaml_record{ $key } = undef;
        if ( $has_yaml ) {
            my $hash;
            my $base    = $self->get_rel_dir;
            my $pkgmeta = $self->get_addon_path->p_node_contents(
                  $base eq ""
                ? &FILE_PKGMETA
                : File::Spec->catfile( $base, &FILE_PKGMETA )
            );
            if ( $pkgmeta ) {
                my @pkg_line = split( /\n/, $pkgmeta );
                if ( $has_tabs ) {
                    $Text::Tabs::tabstop = 8;
                    @pkg_line = Text::Tabs::expand( @pkg_line, "" );
                }
                push @pkg_line, "" unless ( $pkg_line[ -1 ] eq "" );
                my $old = $pkgmeta;
                $pkgmeta = join( "\n", @pkg_line );
                eval {
                    local $SIG{ __WARN__ } = sub { };
                    $hash = ( YAML::Load( $pkgmeta ) );
                };
                if ( $@ ) {
                    my $message = $@;
                    $message =~ s{[\n\r].*}{}s;
                    warn "Unable to read:" . File::Spec->catfile( $self->get_addon_path->get_real_path, $base, &FILE_PKGMETA ) . ": " . $message . "\n";
                    $hash = undef;
                } ## end if ( $@ )
            } ## end if ( $pkgmeta )
            $yaml_record{ $key } = $hash;
        } ## end if ( $has_yaml )
    } ## end unless ( exists $yaml_record...)
    my $hash = $yaml_record{ $key };
    return $hash;
} ## end sub p_get_yaml($ )

sub get_externals($ ) {
    my $self = shift;
    my $hash = $self->p_get_yaml;
    my %res  = ();

    if ( $hash && $hash->{ "externals" } ) {
        while ( my ( $subpath, $value ) = each %{ $hash->{ "externals" } } ) {
            $subpath = File::Spec->catdir( File::Spec::Unix->splitdir( $subpath ) );
            if ( ref $value ) {
                $value = $value->{ "url" };

                # ToDo deal with tag
            }
            $value =~ s{/+$}{};
            $value = fix_external( $value );
            $res{ $subpath } = $value;
        } ## end while ( my ( $subpath, $value...))
        return %res;
    } ## end if ( $hash && $hash->{...})

    my $base     = $self->get_rel_dir;
    my $svn_hash = $self->get_addon_path->p_node_svn_externals( $base );
    if ( $svn_hash ) {
        while ( my ( $subpath, $source ) = each %$svn_hash ) {
            $source = fix_external( $source );
            $res{ $subpath } = $source;
        }
        return %res;
    } ## end if ( $svn_hash )
    return %res;
} ## end sub get_externals($ )

sub get_move_folders($ ) {
    my $self = shift;
    my $hash = $self->p_get_yaml;
    my %res  = ();

    if ( $hash && $hash->{ "move-folders" } ) {
        while ( my ( $subpath, $value ) = each %{ $hash->{ "move-folders" } } ) {
            my @pieces = File::Spec::Unix->splitdir( $subpath );
            shift @pieces;
            $subpath         = File::Spec->catdir( @pieces );
            @pieces          = File::Spec::Unix->splitdir( $value );
            $value           = File::Spec->catdir( @pieces );
            $res{ $subpath } = $value;
        } ## end while ( my ( $subpath, $value...))
    } ## end if ( $hash && $hash->{...})
    return %res;
} ## end sub get_move_folders($ )

sub get_ignored($ ) {
    my $self = shift;
    my $hash = $self->p_get_yaml;

    if ( $hash && $hash->{ "ignore" } ) {
        return map { File::Spec->catdir( File::Spec::Unix->splitdir( $_ ) ) } @{ $hash->{ "ignore" } };
    }
    else {
        return ();
    }
} ## end sub get_ignored($ )

sub force_externals_embedded($ ) {
    my $self = shift;
    my $hash = $self->p_get_yaml;

    if ( $hash && $hash->{ "enable-nolib-creation" } ) {
        return ( $hash->{ "enable-nolib-creation" } =~ m{no}i );
    }
    return undef;
} ## end sub force_externals_embedded($ )

sub get_key($ ) {
    my $self = shift;
    return $self->get_addon_path->p_node_file_key( $self->get_rel_dir );
}

sub get_contents($ ) {
    my $self = shift;
    return $self->get_addon_path->p_node_contents( $self->get_rel_dir );
}

sub replace_contents($$ ) {
    my $self = shift;
    return $self->get_addon_path->p_node_replace_contents( $self->get_rel_dir, $_[ 0 ] );
}

sub copy_to($$ ) {
    my ( $self, $dest ) = @_;

    if ( ref $dest ) {
        return $self->get_addon_path->p_node_copy_to_node(
            $self->get_rel_dir,
            $dest->get_addon_path,
            $dest->get_rel_dir
        );
    } ## end if ( ref $dest )
    else {
        return $self->get_addon_path->p_node_copy_to_path( $self->get_rel_dir, $dest );
    }
} ## end sub copy_to($$ )

sub diff_from($$;$ ) {
    my ( $self, $dest, $check_only ) = @_;

    if ( ref $dest ) {
        return $self->get_addon_path->p_node_diff_from_node(
            $self->get_rel_dir,
            $dest->get_addon_path,
            $dest->get_rel_dir,
            $check_only
        );
    } ## end if ( ref $dest )
    else {
        return $self->get_addon_path->p_node_diff_from_path( $self->get_rel_dir, $dest, $check_only );
    }

} ## end sub diff_from($$;$ )

sub get_virtual_path($ ) {
    my $self = shift;
    return File::Spec->catfile( $self->get_addon_path->get_real_path, $self->get_rel_dir );
}

1;
